package com.gmscosmo.janus.handler;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gmscosmo.janus.util.JanusLogUtil;
import com.gmscosmo.common.JanusPlugin;
import com.gmscosmo.janus.util.JanusUtil;
import com.gmscosmo.model.Participant;
import com.gmscosmo.model.Publisher;
import com.gmscosmo.model.RtpForwardData;
import com.gmscosmo.common.JanusNativeSignaling;
import com.gmscosmo.model.Subscriber;
import com.gmscosmo.janus.lock.RedissLockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
public class JanusIMProtocolProcessor {
    @Autowired
    JanusLogUtil janusLogUtil;
    @Autowired
    JanusUtil janusUtil;
    @Autowired
    JanusVideoRoomRabbitMqEventHandler videoRoomHandler;
    @Autowired
    JanusStreamingRabbitMqEventHandler streamingHandler;

    /**
     * 处理offer和start信令
     * handle offer and start signal
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleMessage(JSONObject jsonObject, String userId) throws Exception {
        String response;
        String messageRequest = jsonObject.getJSONObject("body").getString("request");
        if (messageRequest == null) {
            messageRequest = jsonObject.getJSONObject("body").getJSONObject("body").getString("request");
        }
        int nodeId = janusUtil.getUserNode(userId);
        switch (messageRequest) {
            case JanusNativeSignaling.CONFIGURE:
                response = videoRoomHandler.sendOffer(jsonObject, nodeId);
                break;
            case JanusNativeSignaling.START:
                response = videoRoomHandler.start(jsonObject, nodeId);
                break;
            default:
                jsonObject = new JSONObject();
                jsonObject.put("msg", "未知信令");
                response = jsonObject.toJSONString();
        }
        return response;
    }

    /**
     * 处理create_room,只有发起者会调用
     * handle create_room signal
     * only the room owner could send this signal
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleCreateRoom(JSONObject jsonObject, String userId) throws Exception {
        String roomId = jsonObject.getString("room_id").split("\\|")[1];
        janusUtil.putUserToNode(userId, janusUtil.getFreeNode(roomId));
        if (janusUtil.isUserRoomContainsKey(userId)) {
            String oldRoomId = janusUtil.getUserRoom(userId);
            janusLogUtil.log("用户:" + userId + "在房间:" + oldRoomId + "中", this.getClass(), JanusLogUtil.TRACE);
            if (!oldRoomId.equals(roomId)) {
                janusUtil.leaveRoom(userId);
                janusLogUtil.log(userId + "离开房间:" + oldRoomId, this.getClass(), JanusLogUtil.TRACE);
            }
        }
        janusUtil.putUserRoom(userId, roomId);
        int nodeId = janusUtil.getUserNode(userId);
        long session_id = videoRoomHandler.createSession(userId, nodeId);
        janusLogUtil.log(userId + "创建session_id:" + session_id, this.getClass(), JanusLogUtil.TRACE);
        long handle_id = videoRoomHandler.createHandle(session_id, nodeId, JanusPlugin.VIDEOROOM);
        janusLogUtil.log(userId + "创建handle_id:" + session_id + "插件:" + JanusPlugin.VIDEOROOM, this.getClass(), JanusLogUtil.TRACE);
        String response = videoRoomHandler.createRoom(jsonObject, session_id, handle_id, nodeId);
        janusLogUtil.log(userId + "创建房间:" + roomId, this.getClass(), JanusLogUtil.TRACE);
        return response;
    }

    /**
     * 处理join_pub
     * 把用户添加到某一个节点（根据节点分配策略）
     * 把用户添加到房间中
     * 进入房间（如果是分布式的的时候，房间不存在，则会在新服务器创建一个房间）
     * 并通知远端服务器的其他用户
     * 查询远端服务器的用户，返回到remotePublisher参数中
     * handle join_pub
     * 1.put user to a janus node (depends on the load distribution strategy)
     * 2.put user in the room
     * 3.join room (if in the distribution env, room may be not exist, In this situation, we will create a new room which use the same room id in the new janus node)
     * 4.notify the participant whose are in the remote server
     * 5.get the participant info and return as remotePublisher
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleJoinAndPub(JSONObject jsonObject, String userId) throws Exception {
        String roomId = jsonObject.getString("room_id").split("\\|")[1];
        String key = String.valueOf(roomId);
        RedissLockUtil.tryLock(key, TimeUnit.SECONDS, 10, 3);
        janusLogUtil.log(Thread.currentThread().getName() + "||" + key + "获取锁", this.getClass(), JanusLogUtil.TRACE);
        try {
            if (janusUtil.isUserRoomContainsKey(userId)) {
                String oldRoomId = janusUtil.getUserRoom(userId);
                if (!oldRoomId.equals(roomId)) {
                    janusUtil.leaveRoom(userId);
                    janusLogUtil.log(userId + "离开房间:" + oldRoomId, this.getClass(), JanusLogUtil.TRACE);
                }
            }
            janusUtil.joinRoom(userId, roomId);
            janusLogUtil.log(userId + "加入房间:" + roomId, this.getClass(), JanusLogUtil.TRACE);
            long session_id = jsonObject.getLongValue("session_id");
            long handle_id;
            int nodeId = janusUtil.getUserNode(userId);
            if (0 == session_id) {
                session_id = videoRoomHandler.createSession(userId, nodeId);
                janusLogUtil.log(userId + "创建session_id:" + session_id, this.getClass(), JanusLogUtil.TRACE);
                jsonObject.put("session_id", session_id);
                handle_id = videoRoomHandler.createHandle(session_id, nodeId, JanusPlugin.VIDEOROOM);
                janusLogUtil.log(userId + "创建handle_id:" + session_id + "插件:" + JanusPlugin.VIDEOROOM, this.getClass(), JanusLogUtil.TRACE);
                jsonObject.put("handle_id", handle_id);
            } else {
                handle_id = jsonObject.getJSONObject("body").getLong("handle_id");
                jsonObject.put("handle_id", handle_id);
            }
            if (!videoRoomHandler.roomExist(jsonObject, nodeId)) {
                videoRoomHandler.createRoom(jsonObject, session_id, handle_id, nodeId);
                janusLogUtil.log(userId + "创建房间:" + roomId, this.getClass(), JanusLogUtil.TRACE);
            }
            Long publisher_id = jsonObject.getJSONObject("body").getLong("publisher_id");
            janusUtil.updateParticipant(userId, roomId, session_id, handle_id, publisher_id, userId);
            jsonObject.put("user_id", userId);
            String response = videoRoomHandler.join(jsonObject, "publisher", nodeId);
            janusUtil.notifyRemoteParticipantJoining(userId);
            JSONObject resp = JSONObject.parseObject(response);

            JSONObject plugindata = resp.getJSONObject("plugindata");
            JSONObject data = plugindata.getJSONObject("data");

            JSONArray publishers = data.getJSONArray("publishers");

            List<Participant> remotePublisher = janusUtil.getAVRoomByUserId(userId).oldGetRemoteParticipant(nodeId);
            Publisher publisher;
            for (Participant participant : remotePublisher) {
                janusLogUtil.log("为:"+userId+"添加远端用户:"+participant.getDisplay(), this.getClass(), JanusLogUtil.TRACE);
                publisher = new Publisher();
                publisher.setId(participant.getPublisher_id());
                publisher.setDisplay(participant.getDisplay());
                publisher.setRemote(true);
                publishers.add(publisher);
            }
            data.put("publishers", publishers);
            plugindata.put("data", data);
            resp.put("plugindata", plugindata);
            response = resp.toJSONString();
            return response;
        } finally {
            RedissLockUtil.unlock(key);
            janusLogUtil.log(Thread.currentThread().getName() + "||" + key + "释放锁", this.getClass(), JanusLogUtil.TRACE);
        }
    }

    /**
     * 处理离开
     * 销毁所有rtp转发
     * 离开房间
     * 把用户从节点移除
     * 把用户从房间移除
     * handle leave
     * 1.destroy all rtp forwarder (if exist)
     * 2.leave room
     * 3.remove user from node
     * 4.remove user from room
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleLeave(JSONObject jsonObject, String userId) throws Exception {
        String roomId = janusUtil.getUserRoom(userId);
        String key = roomId;
        RedissLockUtil.tryLock(key, TimeUnit.SECONDS, 10, 3);
        janusLogUtil.log(Thread.currentThread().getName() + "||" + key + "获取锁", this.getClass(), JanusLogUtil.TRACE);
        try {
            int nodeId = janusUtil.getUserNode(userId);
            videoRoomHandler.stopMyRtpForward(userId);
            janusLogUtil.log(userId + "暂停所有rtp转发", this.getClass(), JanusLogUtil.TRACE);
            if (!janusUtil.getIsShareStream()) {
                List<Subscriber> subscribers = janusUtil.getSubscribers(userId);
                for (Subscriber subscriber : subscribers) {
                    videoRoomHandler.stopOtherRtpForward(subscriber.getSubscriberFrom(), userId);
                    janusLogUtil.log(subscriber.getSubscriberFrom() + "暂停所有rtp转发", this.getClass(), JanusLogUtil.TRACE);
                }
                streamingHandler.destroyMyStream(userId);
                janusLogUtil.log(userId + "暂停所有stream接收", this.getClass(), JanusLogUtil.TRACE);
            }
            String response = videoRoomHandler.leave(jsonObject, nodeId);
            janusLogUtil.log(userId + "离开janus房间", this.getClass(), JanusLogUtil.TRACE);
            janusUtil.removeUserFromNode(userId);
            janusLogUtil.log(userId + "离开节点", this.getClass(), JanusLogUtil.TRACE);
            janusUtil.leaveRoom(userId);
            janusLogUtil.log(userId + "离开房间", this.getClass(), JanusLogUtil.TRACE);
            return response;
        } finally {
            RedissLockUtil.unlock(key);
            janusLogUtil.log(Thread.currentThread().getName() + "||" + key + "释放锁", this.getClass(), JanusLogUtil.TRACE);
        }
    }

    /**
     * 处理join_sub订阅
     * 如果订阅的用户不在同一个节点上，
     * 则使用rtp流转发
     * 如果已经为订阅的用户在当前节点创建了一个流接收器
     * 则不需要重复创建
     * 订阅的时候watch参数选择该streamId即可
     * handle join_sub
     * 1.if the user who we want to subscribe is in the different node,
     * in this situation, we could use the rtp forwarder to transport the rtp data from one janus node to the other janus node,
     * but if we already create a rtp forwarder for this user, We can use it directly.
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleJoinAndSub(JSONObject jsonObject, String userId) throws Exception {
        String response;
        long session_id = jsonObject.getLong("session_id");
        long handle_id;
        String display = jsonObject.getJSONObject("body").getString("displayId");
        jsonObject.put("session_id", session_id);
        int nodeId = janusUtil.getUserNode(userId);
        if (nodeId != janusUtil.getUserNode(display)) {
            handle_id = streamingHandler.createHandle(session_id, nodeId, JanusPlugin.STREAMING);
            janusLogUtil.log(userId + "创建handle_id:" + session_id + "插件:" + JanusPlugin.STREAMING, this.getClass(), JanusLogUtil.TRACE);
            jsonObject.put("handle_id", handle_id);
            int roomId = Integer.parseInt(jsonObject.getString("room_id").split("\\|")[1]);
            int streamId = 0;
            if (janusUtil.getIsShareStream()) {
                streamId = janusUtil.getStreamId(display, janusUtil.getNodeInfo(nodeId).getHost(), roomId);
            }
            if (streamId == 0) {
                RtpForwardData rtpForwardData = videoRoomHandler.createRtpForwardRequest(userId);
                //streamingHandler.sendRtpForwardRequest(display,joinEvent);
                //创建接收流
                streamId = rtpForwardData.getId();
                streamingHandler.createStream(session_id, handle_id, display, streamId, rtpForwardData, userId, nodeId);
                videoRoomHandler.handleRtpForwardRequest(display, userId, rtpForwardData);
            }
            //选择观看流
            response = streamingHandler.watch(session_id, handle_id, streamId, nodeId);
        } else {
            handle_id = videoRoomHandler.createHandle(session_id, nodeId, JanusPlugin.VIDEOROOM);
            janusLogUtil.log(userId + "创建handle_id:" + session_id + "插件:" + JanusPlugin.VIDEOROOM, this.getClass(), JanusLogUtil.TRACE);
            jsonObject.put("handle_id", handle_id);
            response = videoRoomHandler.join(jsonObject, "subscriber", nodeId);
        }
        String roomId = jsonObject.getString("room_id").split("\\|")[1];
        janusUtil.updateParticipant(userId, roomId, session_id, handle_id, 0L, display);
        return response;
    }

    /**
     * 处理心跳，保持活跃
     * handle keepalive signal to keep the session active
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleKeepALive(JSONObject jsonObject, String userId) throws Exception {
        return videoRoomHandler.keepalive(jsonObject, janusUtil.getUserNode(userId));
    }

    /**
     * 处理收集候选者
     * handle collect candidates (webRTC)
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleCandidate(JSONObject jsonObject, String userId) throws Exception {
        return videoRoomHandler.sendCandidate(jsonObject, janusUtil.getUserNode(userId));
    }

    /**
     * 重新协商ice
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleRestartICE(JSONObject jsonObject, String userId) throws Exception {
        return videoRoomHandler.sendRestartICE(jsonObject, janusUtil.getUserNode(userId));
    }

    /**
     * 处理媒体协商，分辨率，订阅流层等等
     * handle configure video substream,temporal etc.
     *
     * @param jsonObject
     * @param userId
     * @return
     * @throws Exception
     */
    public String handleConfigure(JSONObject jsonObject, String userId) throws Exception {
        return videoRoomHandler.configure(jsonObject, janusUtil.getUserNode(userId));
    }
}
